﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using EnsureThat;

namespace Robo
{
	/// <summary>
	/// A static class containing task-related extensions.
	/// </summary>
	public static class TaskHelper
	{
		/// <summary>
		/// Wraps a System.Threading.WaitHandle <paramref name="handle"/> in an awaitable task, using given timeout.
		/// </summary>
		public static Task<bool> AsTask(this WaitHandle handle, int timeout = -1)
		{
			Ensure.That(() => handle, Is.NotNull);
			TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
			RegisteredWaitHandle token = null;
			var tokenReady = new ManualResetEventSlim();
			token = ThreadPool.RegisterWaitForSingleObject(
				handle,
				(state, timedOut) =>
				{
					tokenReady.Wait();
					tokenReady.Dispose();
					token.Unregister(handle);
					tcs.SetResult(!timedOut);
				},
				null,
				timeout,
				true);
			tokenReady.Set();
			return tcs.Task;
		}
		/// <summary>
		/// Gets the awaiter for the WaitHandle.
		/// </summary>
		public static TaskAwaiter<bool> GetAwaiter(this WaitHandle handle)
		{
			return handle.AsTask().GetAwaiter();
		}
	}

	/// <summary>
	/// Provides general extensions.
	/// </summary>
	public static class Extensions
	{
		#region Enumerator
		/// <summary>
		/// Wraps an enumerator given its enumerator functions.
		/// </summary>
		public static IEnumerable<T> Wrap<T>(Func<bool> moveNext, Func<T> getCurrent, Action dispose = null)
		{
			Ensure.That(() => moveNext, Is.NotNull);
			Ensure.That(() => getCurrent, Is.NotNull);
			try
			{
				while (moveNext())
					yield return getCurrent();
			}
			finally
			{
				if (dispose != null)
					dispose();
			}
		}

		/// <summary>
		/// Wraps an T:System.Collectioons.Generic.IEnumerator{T} instance in an enumerable. Shrotcut for calling <see cref="Wrap{T}(System.Func{System.Boolean}, System.Func{T}, System.Action)"/>.
		/// </summary>
		/// <seealso cref="Wrap{T}(System.Func{System.Boolean}, System.Func{T}, System.Action)"/>
		public static IEnumerable<T> Wrap<T>(this IEnumerator<T> enumerator)
		{
			Ensure.That(() => enumerator, Is.NotNull);
			return Wrap(enumerator.MoveNext, () => enumerator.Current, enumerator.Dispose);
		}

		/// <summary>
		/// Wraps an T:System.Collectioons.IEnumerator. Shortcut for calling <see cref="Wrap{T}(System.Func{System.Boolean}, System.Func{T}, System.Action)"/>.
		/// </summary>
		/// <seealso cref="Wrap{T}(System.Func{System.Boolean}, System.Func{T}, System.Action)"/>
		public static IEnumerable Wrap(this IEnumerator enumerator)
		{
			Ensure.That(() => enumerator, Is.NotNull);
			return Wrap(enumerator.MoveNext, () => enumerator.Current, null);
		}
		#endregion
		/// <summary>
		/// Strongly typed version of <see cref="SerializationInfo.GetValue(string, System.Type)"/>
		/// </summary>
		public static T GetValue<T>(this SerializationInfo info, string name)
		{
			Ensure.That(() => info, Is.NotNull);
			Ensure.That(() => name, StringIs.NotNullOrWhiteSpace);
			return (T)info.GetValue(name, typeof(T));
		}
		#region Enumerable Query
		/// <summary>
		/// Delimates a collection of data.
		/// </summary>
		public static string Delimit<TSource>(this IEnumerable<TSource> source, string delimiter)
		{
			Ensure.That(() => source, Is.NotNull);
			Ensure.That(() => delimiter, StringIs.NotNullOrWhiteSpace);
			return string.Join(delimiter, source);
		}
		/// <summary>
		/// Delimates a collection of data, using <paramref name="selector"/> to select the data.
		/// </summary>
		public static string Delimit<TSource>(this IEnumerable<TSource> source, Func<TSource, string> selector, string delimiter)
		{
			if (source == null)
				throw new ArgumentNullException("source");
			if (selector == null)
				throw new ArgumentNullException("selector");
			if (delimiter == null || string.IsNullOrEmpty(delimiter.Trim()))
				throw new ArgumentNullException("delimiter");
			return source.Select(selector).Delimit(delimiter);
		}
		/// <summary>
		/// Splits the collection, using the given delimiter.
		/// </summary>
		/// <remarks>Delegates to <see cref="Split{TSource}(IEnumerable{TSource}, TSource, IEqualityComparer{TSource})"/></remarks>
		public static IEnumerable<IEnumerable<TSource>> Split<TSource>(this IEnumerable<TSource> source, TSource delimiter)
		{
			Ensure.That(() => source, Is.NotNull);
			return source.Split(delimiter, EqualityComparer<TSource>.Default);
		}
		/// <summary>
		/// Splits the collection, using the given delimiter and equality comparer.
		/// </summary>
		public static IEnumerable<IEnumerable<TSource>> Split<TSource>(this IEnumerable<TSource> source, TSource delimiter, IEqualityComparer<TSource> comparer)
		{
			Ensure.That(() => source, Is.NotNull);
			comparer = comparer ?? EqualityComparer<TSource>.Default;
			using (IEnumerator<TSource> rator = source.GetEnumerator())
			{
				bool flag = true;
				IEnumerable<TSource> ret = null;
				do
				{
					if (flag)
					{
						flag = false;
						continue;
					}
					yield return ret;
				}
				while (SplitBody(rator, o => comparer.Equals(o, delimiter), out ret));
			}
		}
		[DebuggerHidden]
		[DebuggerStepThrough]
		private static bool SplitBody<TSource>(IEnumerator<TSource> rator, Predicate<TSource> pred, out IEnumerable<TSource> ret)
		{
			Ensure.That(() => rator, Is.NotNull);
			List<TSource> list = new List<TSource>();
			bool flag = false;
			while (rator.MoveNext())
			{
				var item = rator.Current;
				list.Add(item);
				if (pred(item))
				{
					flag = true;
					break;
				}
			}
			ret = list.Select(x => x);
			return flag;
		}

		/// <summary>
		/// Performs an action on all items in a collection.
		/// </summary>
		public static void ForAll<T>(this IEnumerable<T> source, Action<T> action)
		{
			Ensure.That(() => source, Is.NotNull, CollectionIs.NotEmpty);
			foreach (var item in source)
				action(item);
		}
		#endregion
	}
}
